package com.mybatis.boost.id;

import com.mybatis.boost.tool.InetUtil;

import java.net.UnknownHostException;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.String.format;

/**
 * 分布式ID生成器
 * Created by zhuwei on 2016/12/17.
 */
public class IdGenerator {

    private final ReentrantLock lock = new ReentrantLock();

    private final long nodeId;
    private final long twepoch = 1288834974657L; //基准时间戳：Thu, 04 Nov 2010 01:42:54 GMT
    private final long nodeIdBits = 10L; //节点ID长度
    private final long sequenceBits = 12L; //序列号12位
    private final long nodeIdShift = sequenceBits; //机器节点左移12位
    private final long timestampLeftShift = nodeIdShift + nodeIdBits; //时间毫秒数左移22位
    private final long maxNodeId = ~(-1L << nodeIdBits); //节点ID取值范围：[0,1023]
    private final long sequenceMask = ~(-1L << sequenceBits); //序列号取值范围：[0,4095]

    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public IdGenerator() {
        this.nodeId = getDefaultNodeId();
    }

    public IdGenerator(long nodeId) {
        if (nodeId > maxNodeId || nodeId < 0) {
            throw new IllegalArgumentException(format("node Id can't be greater than %d or less than 0", nodeId));
        }
        this.nodeId = nodeId;
    }

    public long nextId() {
        lock.lock();
        try {
            long timestamp = timeGen(); //获取当前毫秒数
            //如果服务器时间有问题(时钟后退) 报错。
            if (timestamp < lastTimestamp) {
                throw new RuntimeException(format(
                        "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
            }
            //如果上次生成时间和当前时间相同,在同一毫秒内
            if (lastTimestamp == timestamp) {
                //sequence自增，因为sequence只有12bit，所以和sequenceMask相与一下，去掉高位
                sequence = (sequence + 1) & sequenceMask;
                //判断是否溢出,也就是每毫秒内超过4095，当为4096时，与sequenceMask相与，sequence就等于0
                if (sequence == 0) {
                    timestamp = tilNextMillis(lastTimestamp); //自旋等待到下一毫秒
                }
            } else {
                sequence = 0L; //如果和上次生成时间不同,重置sequence，就是下一毫秒开始，sequence计数重新从0开始累加
            }
            lastTimestamp = timestamp;

            // 000000000000000000000000000000000000000000  0000000000       000000000000
            // time                                        nodeId           sequence
            return ((timestamp - twepoch) << timestampLeftShift) | (nodeId << nodeIdShift) | sequence;
        } finally {
            lock.unlock();
        }
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    private long getDefaultNodeId() {
        try {
            int ipAddressAsInt = InetUtil.getIpAddressAsInt();
            return Math.abs(ipAddressAsInt) % (maxNodeId + 1);
        } catch (UnknownHostException e) {
            throw new IllegalArgumentException("nodeId initialized by unknown host");
        }
    }
}
